package tacos.data;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import tacos.TacoOrder;

import java.util.Date;
import java.util.List;

/*
 * 因为CrudRepository已经定义了我们需要的方法，所以没有必要在IngredientRepository和OrderRepository中显式定义它们了。
 * 你可能会想，我们接下来需要为这两个存储库（甚至包括CrudRepository中定义的十多个方法）编写实现了。但是，关于Spring Data的好消息是：
 * 我们根本不需要编写任何实现！当应用启动的时候，Spring Data会在运行时自动生成一个实现。这意味着存储库已经准备就绪，我们将其注入控制器就可以了。
 */
public interface OrderRepository extends CrudRepository<TacoOrder, Long> {
    /*
    * 自定义JPA存储库。
    * 生成存储库实现时，Spring Data会检查存储库接口的所有方法，解析方法的名称，并基于被持久化的对象（如本例中的TacoOrder）来试图推测方法的目的。
    * 在本质上，Spring Data定义了一组小型的领域特定语言(Domain-Specific Language, DSL)，在这里，持久化的细节都是通过存储库方法的签名来描述的。
    * Spring Data能够知道这个方法要查找TacoOrder，因为我们使用TacoOrder对CrudRepository进行了参数化。
    * 方法名findByDeliveryZip()确定该方法需要根据deliveryZip属性匹配来查找TacoOrder，而deliveryZip的值是作为参数传递到方法中来的。
    * Spring Data也能处理更加有意思的方法名称。存储库的方法由一个动词、一个可选的主题(subject)、关键词By，以及一个断言组成。
    * 在findByDeliveryZip()这个样例中，动词是find，断言是DeliveryZip，主题并没有指定，暗含的主题是TacoOrder。
    * */
    List<TacoOrder> findByDeliveryZip(String deliveryZip);

    /*
    * 一个更复杂的样例。假设我们想要查找投递到指定邮编且在一定时间范围内的订单。
    * Spring Data会将get、read和find视为同义词，它们都是用来获取一个或多个实体的。另外，我们还可以使用count作为动词，它会返回一个int值，代表了匹配实体的数量。
    * 你可以将方法命名为“readPuppiesBy...”，而它依然会去查找TacoOrder实体，因为CrudRepository是使用TacoOrder类进行参数化的。
    * By后面的断言是方法签名中最为有意思的一部分。在本例中，断言指定了TacoOrder的两个属性：deliveryZip和placedAt。
    * deliveryZip属性的值必须要等于方法第一个参数传入的值。关键字Between表明placedAt属性的值必须要位于方法最后两个参数的值之间。
    * 除了Equals和Between操作之外，Spring Data方法签名还能包括如下的操作符：
    * ●IsAfter、After、IsGreaterThan、GreaterThan；
    * ●IsGreaterThanEqual、GreaterThanEqual；
    * ●IsBefore、Before、 IsLessThan、LessThan；
    * ●IsLessThanEqual、LessThanEqual；
    * ●IsBetween、Between ；
    * ●IsNull、Null；
    * ●IsNotNull、NotNull；
    * ●IsIn、In；
    * ●IsNotIn、NotIn；
    * ●IsStartingWith、StartingWith、StartsWith；
    * ●IsEndingWith、EndingWith、EndsWith；
    * ●IsContaining、Containing、Contains；
    * ●IsLike、Like；●IsNotLike、NotLike；
    * ●IsTrue、True；
    * ●IsFalse、False；
    * ●Is、Equals；
    * ●IsNot、Not；
    * ●IgnoringCase、IgnoresCase。
    * */
    List<TacoOrder> readOrdersByDeliveryZipAndPlacedAtBetween(String deliveryZip, Date startDate, Date endDate);

    // 作为IgnoringCase或IgnoresCase的替代方案，我们还可以在方法上添加AllIgnoringCase或AllIgnoresCase，这样它就会忽略所有String对比的大小写。
    List<TacoOrder> findByDeliveryNameAndDeliveryCityIgnoreCase(
            String deliveryName, String deliveryCity);

    // 还可以在方法名称的结尾处添加OrderBy，使结果集根据某个列排序。例如，我们可以按照deliveryName属性排序：
    List<TacoOrder> findByDeliveryCityOrderByDeliveryName(String city);

    // 对于复杂的查询，方法名可能会面临失控的风险。可以将方法定义为任何想要的名称，然后为方法添加@Query注解，从而明确指明方法调用时要执行的查询，如下所示：
    @Query("SELECT o from TacoOrder o where o.deliveryCity = 'Seattle'")
    List<TacoOrder> readOrdersDeliveredInSeattle();
}
